<?php

class Materials_Model_Material extends Workset_Model_Object_Abstract implements IteratorAggregate
{

    const TYPE = 'materials_material';

    const FLAG_VALUE_FILE = 1;
    
    const IS_NEW_PERIOD = 604800; // 1 week

    const STATUS_PUBLISHED = 1;

    const STATUS_DRAFT = 0;

    private static $_fileTypes = array('file', 'image');

    protected $_hasRelations = true;

    protected $_objectType = self::TYPE;

    protected $_categories = array();

    protected $_categoriesToDelete = array();

	protected $_catalogueCategories = array();

	protected $_catalogueCategoriesToDelete = array();

    protected $_comments = array();

    protected $_labels = array();

	protected $_contentBlock = null;

	protected $_contentUpdate = false;

    /**
     * Возвращает объект Materials_Model_Material по primary key
     *
     * @static
     * @param  mixed $identity
     * @return Materials_Model_Material
     *
     */
	static function getInstance($identity) {
		$storage = self::getDefaultStorage(self::TYPE);
		return self::getFromStorage($storage, $identity);
	}

    /**
     * Создает новый объект Materials_Model_Material с полями $data
     *
     * @static
     * @param  array $data
     * @return Materials_Model_Material
     *
     */
	static function create($data) {
		$storage = self::getDefaultStorage(self::TYPE);
		return $storage->create(self::getInputData($data));
	}

	/**
     * Возвращает список материалов
     *
     * @static
     * @param  mixed $where
     * @param  mixed $order
     * @param  int $limit
     * @param  int $offset
     * @return Workset_Model_Object_Iterator
     *
     */
    static function getAll($where = null, $order = null, $limit = null, $offset = null, $offsetByPage = false) {
        $storage = self::getDefaultStorage(self::TYPE);
        return $storage->getAll($where, $order, $limit, $offset, $offsetByPage);
    }
    
    /**
	 * Возвращает количество объектов запроса
     */
	static function getCount($where = null, $order = null, $limit = null, $offset = null, $offsetByPage = false) {
        $storage = self::getDefaultStorage(self::TYPE);
        return $storage->getCount($where, $order, $limit, $offset, $offsetByPage);
    }

    /**
     * Возвращает список материалов отсортированный по популярности привязанного раздела каталога
     */
    static function getPopular($where = null, $order = null, $limit = null, $offset = null, $offsetByPage = false) {

        if (empty($order)) {
            $order = 'rating DESC';
        } else {
            $order = (array) $order;
            array_unshift($order, 'rating DESC');
        }

        $storage = self::getDefaultStorage(self::TYPE);
        return $storage->getAll($where, $order, $limit, $offset, $offsetByPage);
    }

    /**
     * Возвращает материал с проверкой статуса публикации 
     *
     * @static
     * @throws Workset_Model_Exception
     * @param  $id
     * @param bool $checkPublished
     * @return Materials_Model_Material
     */
    static function getPublishedMaterial($id, $checkPublished = true) {
        $where = array('id = ?' => $id);

        if (true == $checkPublished) {
            $where = array(
                'id = ?' => $id,
                'status = ?' => self::STATUS_PUBLISHED,
                'pub_date_start is null OR pub_date_start <= NOW()'
            );
        }

        $result = self::getAll($where);

        if ($result->count() > 0) {
            return $result->current();
        } else {
            throw new Workset_Model_Exception("Material with ID = $id not found");
        }
    }

    /**
     * Возвращает допустимые типы файлов
     *
     * @static
     * @return array
     */
    static function getFileTypes() {
        return self::$_fileTypes;
    }

    /**
     * Инициализация объекта
     *
     * @return void
     */
    public function init() {
        $this->attach(new Materials_Model_Material_Observer_SearchContentUpdater());
        $this->attach(new Materials_Model_Material_Observer_CategoryUpdater());
        $this->attach(new Materials_Model_Material_Observer_LabelRankUpdater());
        $this->attach(new Materials_Model_Material_Observer_CommentsStatusUpdater());
        //$this->attach(new Materials_Model_Material_Observer_CategoryStatusUpdater());
    }

    /**
     * Добавляет связь материала с категорией
     *
     * @param  $category
     * @return Materials_Model_Object
     */
    public function addCategory($category) {
        if (!$category instanceof Materials_Model_Category) {
			$category = Materials_Model_Category::getInstance($category);
		}

        if ($category) {
			$id = $category->getId();
			if ($id > 0 && !isset($this->_categories[$id])) {
				$this->_categories[$id] = $category;
                $this->_update = true;
			}
        }

		return $this;
    }

    /**
     * Удаляет связь материала с категорией
     *
     * @param  $category
     * @return Catalogue_Model_Object
     */
    public function deleteCategory($category) {
        if (!$category instanceof Materials_Model_Category) {
			$category = Materials_Model_Category::getInstance($category);
		}

        if ($category) {
            $this->_categoriesToDelete[] = $category;
            $this->_update = true;
        }

        return $this;
    }

    /**
     * Добавляет комментарий к материалу
     *
     * @param Materials_Model_Comment $comment
     * @return Materials_Model_Material
     */
    public function addComment(Materials_Model_Comment $comment) {

        $comment->setMaterial($this);

        $this->_comments[] = $comment;
        $this->_update = true;

        return $this;
    }

    /**
     * Добавляет метку к материалу
     *
     * @param Materials_Model_Label $label
     * @return Materials_Model_Material
     */
    public function addLabel(Materials_Model_Label $label) {

        $this->_labels[] = $label;
        $this->_update = true;

        return $this;
    }

    /**
     * Возвращает название материала
     *
     * @return string
     */
    public function getTitle() {
        return $this['title'];
    }

    /**
     * Возвращает ID владельца материала
     *
     * @return int
     */
    public function getOwnerId() {
        return (int)$this['owner_id'];
    }

    /**
     * Возвращает дату публикации материала
     *
     * @return int
     */
    public function getPubDate() {
        if ($this['pub_date_start'] > 0) {
            return (int)$this['pub_date_start'];
        } else {
            return (int)$this['created'];
        }
    }

    /**
     * Проверяет, является ли материал опубликованным
     *
     * @return bool
     */
    public function isPublished() {
        return $this['status'] == self::STATUS_PUBLISHED && $this->getPubDate() <= time();
    }

	/**
	 * Содержит ли материал файлы
	 *
	 * @return bool
	 */
	public function hasFiles() {
		return $this['files'] > 0;
	}

	/**
	 * Возвращает количество файлов, которое содержит материал
	 *
	 * @return int
	 */
	public function filesCount() {
		return (int)$this['files'];
	}

	/**
	 * Хранится ли содержимое материала в виде сериализованного объекта
	 *
	 * @return bool
	 */
	public function isContentSerialized() {
		return (bool)$this['serialize'];
	}

    /**
     * Возвращает ID категории.
     *
     * @return int
     */
    public function getCategoryId() {
        if ($this->_data['category_id'] > 0) {
            return (int)$this->_data['category_id'];
        } else {
            return null;
        }
    }

    /**
     * Возвращает категорию материала.
     *
     * @return Catalogue_Model_Object
     */
    public function getCategory() {
        $id = $this->getCategoryId();
        if (null !== $id) {
            return Materials_Model_Category::getInstance($id);
        } else {
            return null;
        }
    }

    /**
     * Устанавливает категорию для материала
     *
     * @throws Workset_Model_Exception
     * @param  Materials_Model_Category|int $category
     * @return Materials_Model_Material
     */
    public function setCategory($category) {
        if ($category instanceof Materials_Model_Category) {
            $category = $category->getId();
        } elseif (!isInt($category)) {
            throw new Workset_Model_Exception('Category must be an instance of Materials_Model_Category or int');
        }

        $this['category_id'] = $category;
        return $this;
    }

    /**
     * Устанавливает теги для материала.
     *
     * @param  string|array $tags
     * @return Materials_Model_Material
     */
    public function setTags($tags) {
        if (is_array($tags)) {
            $tags = implode(',', $tags);
        }

        $this['tags'] = $tags;
        return $this;
    }

	/**
	 * Возвращает содержимое материала.
	 * Если не указано сериализовывать данные (флаг serialize = 0),
	 * то данные возвращаются ввиде строки, иначе возвращается объект
	 * Materials_Model_Material_Block 
	 *
     * @param  $asObject
	 * @return Materials_Model_Material_Block
	 */
	public function getContent() {
		if (!$this->isContentSerialized()) {
			return $this->_data['content'];
			
		} else {
            return $this->_getContentObject();
        }
	}

    protected function _getContentObject() {
        if (null === $this->_contentBlock) {
            
			$this->_contentBlock = unserialize($this->_data['content']);

			if (!$this->_contentBlock instanceof Materials_Model_Material_Block) {
				throw new Workset_Model_Exception('Invalid type of material content');
			}
		}

		return $this->_contentBlock;
    }

	/**
	 * Устанавливает содержимое материала.
	 * Если не указано сериализовывать данные (флаг serialize = 0),
	 * то содержимое передается в виде строки, иначе можно передать
	 * готовый объект Materials_Model_Material_Block
	 * 
	 * @param  $content
	 * @return Materials_Model_Material
	 */
	public function setContent($content) {
		if (!$this->isContentSerialized()) {
            if (is_string($content)) {
                $this->_data['content'] = $content;
            } else {
                throw new Workset_Model_Exception('Content must be a string');
            }
            
		} elseif ($content instanceof Materials_Model_Material_Block) {
			$this->_contentBlock = $content;

		} else {
			$this->_contentBlock = new Materials_Model_Material_Block($content);
		}

		$this->_update = true;
		
		return $this;
	}

    /**
     * Возвращает список комментариев
     *
     * @param  $filter
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @param bool $offsetByPage
     * @return Workset_Model_Object_Iterator
     */
    public function getComments($filter = null, $order = null, $limit = null, $offset = null, $offsetByPage = false) {
        if (empty($filter)) {
            $filter = array('material_id = ?' => $this->getId());
        } else {
            $filter = (array)$filter;
            $filter['material_id = ?'] = $this->getId();
        }

        return Materials_Model_Comment::getAll($filter, $order, $limit, $offset, $offsetByPage);
    }

    /**
     * @see Workset_Model_Object_Abstract::setData()
     * @param  $data
     * @return Materials_Model_Material
     */
	public function setData($data) {
		$content = null;

		if (is_array($data) || $data instanceof Workset_Model_Row_Interface) {
			if (isset($data['content'])) {
				$content = $data['content'];
				unset($data['content']);
			}
		}
		//для совместимости с принимаемыми типами данных в родительском классе
		//IteratorAggregate и Iterator
		else {
			$tmp = array();

			if ($data instanceof IteratorAggregate) {
				$data = $data->getIterator();
				
			}
            //в этом случае передавать нечего, но хотелось бы, чтобы родитель обработал эту ситуацию
            elseif (!$data instanceof Iterator) {
				return parent::setData(null);
			}

			foreach ($data as $key => $val) {
				if ($key != 'content') {
					$tmp[$key] = $val;
				} else {
					$content = $val;
				}
			}

			$data = $tmp;

		}

		parent::setData(self::getInputData($data));

		if (null !== $content) {
			$this->setContent($content);
		}
	}

    /**
     * @see Workset_Model_Object_Abstract::save()
     */
    public function save() {

		if ($this['serialize'] && $this->_contentBlock) {
			$this->_data['content'] = serialize($this->_contentBlock);
		}

        $result = parent::save();

        if ($result) {
            $this->_updateObjects();
        }

        return $result;
    }

    /**
     * @see Workset_Model_Object_Abstract::delete()
     */
    public function delete() {
		
        if (!$this->dropFiles()) {
            throw new Workset_Model_Exception('Cannot delete material files');
        }

        $categories = $this->getCategories();
        
        $result = parent::delete();

        if ($result) {
            foreach ($categories as $category) {
                $category->decrementMaterials()->updateData();
            }
        }

        return $result;
    }

	/**
     * @see Workset_Model_Object_Abstract::updateData()
     */
    public function updateData() {
        if ($this->getId() > 0) {

            $this->_updateObjects();
            
            if (true == $this->_update) {
                return $this->save();
            }
        }
    }

    /**
     * Удаляет файлы, принадлежащие материалу
     * 
     * @param  $files
     * @return bool
     */
    public function dropFiles($files = null) {
        if ($this['serialize'] && $this['files'] > 0 ) {
            if (null === $files) {
                $files = $this->getContent()->filter(self::getFileTypes());
                if ($files->count() > 0) {
                    $files = arrayStraight($files->toArray(), 1);
                    $this['files'] = 0;
                }
            }
            parent::dropFiles($files);
        }
        return true;
    }

    protected function _updateObjects() {
        if ($this->_categoriesToDelete) {
            foreach ($this->_categoriesToDelete as $category) {
                $this->unlinkObject($category);
            }
        }

        if ($this->_categories) {
            foreach ($this->_categories as $category) {
                $this->linkToObject($category);
            }
        }

		if ($this->_catalogueCategoriesToDelete) {
			foreach ($this->_catalogueCategoriesToDelete as $category) {
				$this->getStorage()->deleteCatalogueCategory($category);
			}
		}

		if ($this->_catalogueCategories) {
			foreach ($this->_catalogueCategories as $category) {
				$this->getStorage()->addCatalogueCategory($category);
			}
		}

        if ($this->_comments) {
            foreach ($this->_comments as $comment) {
                $comment->setMaterial($this);
                $comment->save();
			}
        }

        if ($this->_labels) {
            foreach ($this->_labels as $label) {
                $this->linkToObject($label);
			}
        }

        $this->_clean();
    }

    /**
     * @see Workset_Model_Object_Abstract::linkToObject()
     * @param Workset_Model_Object_Abstract $object
     * @return bool
     */
    public function linkToObject(Workset_Model_Object_Abstract $object) {
        $result = parent::linkToObject($object);

        if ($result) {
            if ($object instanceof Materials_Model_Category) {
                $object->incrementMaterials()->updateData();
            }
        }

        return $result;
    }

    /**
     * @see Workset_Model_Object_Abstract::unlinkObject()
     * @param Workset_Model_Object_Abstract $object
     * @return bool
     */
    public function unlinkObject(Workset_Model_Object_Abstract $object) {
        $result = parent::unlinkObject($object);

        if ($result) {
            if ($object instanceof Materials_Model_Category) {
                $object->decrementMaterials()->updateData();
            }
        }

        return $result;
    }

    /**
     * Вовзращает список категорий материала
     *
     * @return Workset_Model_Object_Iterator
     */
    public function getCategories() {
        $storage = $this->getStorage();
        return $storage->getCategories($this);
    }

	/**
     *
     * Implements ArrayAccess Interface
     * @param  $key
     * @param  $value
     * @return void
     */
	public function offsetSet($key, $value) {
		if ($key == 'content') {
			$this->setContent($value);
		} else {
			parent::offsetSet($key, $value);
		}
	}
	
	/**
	 * Implements ArrayAccess Interface
	 *
	 * @param  $key
	 * @return Workset_Model_ObjectInterface
	 */
	public function offsetGet($key) {
		if ($key == 'content') {
			return $this->getContent();
		} else {
			return parent::offsetGet($key);
		}
	}

	/**
	 * Implements IteratorAggregate Interface
	 *
	 * @param  $key
	 * @return Workset_Model_ObjectInterface
	 */
    public function getIterator() {
        if ($this->_data['serialize']) {
            return $this->_getContentObject();
        } else {
            throw new Workset_Model_Exception('String cannot be iteratable');
        }
    }

    /**
     * Чистит временные данные
     *
     * @return void
     */
    protected function _clean() {
        $this->_categories = array();
        $this->_categoriesToDelete = array();
		$this->_contentBlock = null;
    }

    /**
     * Преобразует поля объекта во внутренний формат (для хранения в БД)
     *
     * @static
     * @param
     * @return array
     *
     */
    static function getInputData($data, $key = null) {
        if (null !== $key) {
            $inputData = array($key => $data[$key]);
        } elseif ($data instanceof Workset_Model_Object_Interface) {
            $inputData = $data->toArray();
        } else {
            $inputData = $data;
        }

        foreach ($inputData as $key => $value) {
            switch ($key) {
                case 'pub_date_start':
                case 'pub_date_end':
                    $inputData[$key] = self::inputDateFormat($value);
                break;
            }
        }

        return $inputData;
    }

    /**
     * Преобразует поля для отображения на сайте
     *
     * @static
     * @param  $data
     * @param  $key
     * @return array
     *
     */
    static function getOutputData($data, $key = null) {
        if (null !== $key) {
            $outputData = array($key => $data[$key]);
        } elseif ($data instanceof Workset_Model_Object_Interface) {
            $outputData = $data->toArray();
        } else {
            $outputData = $data;
        }

        foreach ($outputData as $key => $value) {
            switch ($key) {
                case 'pub_date_start':
                case 'pub_date_end':
                    $outputData[$key] = self::outputDateFormat($value, null, self::$_dateFormat);
                break;
            }
        }

        return $outputData;
    }
    
    
    /**
     * Определение новизны материала
     * @return boolean
     */
    function isNew() {
    	return ( (int) $this['pub_date_start'] + self::IS_NEW_PERIOD ) > time();
    }

}

?>